/* Copyright (c) 2010, mitsuhisa.net
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials provided
 *       with the distribution.
 *     * Neither the name of the misuhisa.net nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <algorithm>
#include "GeneralObject.hpp"
#include "String.hpp"
#include "Handle.hpp"

namespace nyu {

namespace {

struct KeyHashFunc {
    Context& c_;

    KeyHashFunc(Context& c) : c_(c) {
    }

    Object::hash_code_type operator()(Value key) {
        if (!is_object(key) || is_null(key))
            return key;
        return value_to_object(key)->hash(c_);
    }

    bool operator()(Value k1, Value k2) {
        if (is_object(k1)) {
            if (is_null(k1))
                return is_null(k2);
            return value_to_object(k1)->opEq(k2, c_);
        }

        if (is_object(k2)) {
            if (is_null(k2))
                return false;
            k2 = value_to_object(k2)->toInt(c_);
        }

        return k1 == k2;
    }
};

struct KeyHashFunc2 {
    Object::hash_code_type operator()(Value key) {
        if (!is_object(key) || is_null(key))
            return key;
        return value_to_object(key)->hash();
    }

    bool operator()(Value k1, Value k2) {
        return k1 == k2;
    }
};

struct UnrefFunc {
    void operator()(Value v) const {
        if (is_object(v) && !is_null(v))
            value_to_object(v)->unref();
    }
};

struct EachChildrenValueFunc {
    const HeapObject::EachChildrenFunc& fn_;

    EachChildrenValueFunc(const HeapObject::EachChildrenFunc& fn) :
        fn_(fn) {
    }

    void operator()(Value v) const {
        if (is_object(v) && !is_null(v))
            fn_(value_to_object(v));
    }
};

}

GeneralObject::GeneralObject() {
    addSlot(String::PROTO, static_cast<Object*>(0));
}

GeneralObject::GeneralObject(Value proto) {
    addSlot(String::PROTO, proto);
}

GeneralObject::GeneralObject(Object* proto) {
    addSlot(String::PROTO, proto);
}

GeneralObject::~GeneralObject() {
    slot_tbl_.eachKeys(UnrefFunc());
    std::for_each(slots_.begin(), slots_.end(), UnrefFunc());
}

Value GeneralObject::getSlot(Value name, Context& c) {
    slots_type::size_type i;
    if (slot_tbl_.get(name, i, KeyHashFunc(c))) {
        return slots_[i];
    }

    Value proto = slots_[0];
    if (object_to_value(this) == proto || !is_object(proto) || is_null(proto))
        return NULL_VALUE;

    return value_to_object(proto)->getSlot(name, c);
}

void GeneralObject::putSlot(Value name, Value value, Context& c) {
    slots_type::size_type i;
    if (slot_tbl_.get(name, i, KeyHashFunc(c))) {
        Value& v = slots_[i];
        if (is_object(v) && !is_null(v))
            value_to_object(v)->unref();
        v = value;

        if (is_object(name) && !is_null(name))
            value_to_object(name)->unref();

        return;
    }

    i = slots_.size();
    slots_.push_back(value);
    slot_tbl_.put(name, i, KeyHashFunc(c));
}

void GeneralObject::addSlot(String* name, Value value) {
    assert(name);

    name->ref();
    if (is_object(value) && !is_null(value))
        value_to_object(value)->ref();

    slots_type::size_type i = slots_.size();
    slots_.push_back(value);
    slot_tbl_.put(object_to_value(name), i, KeyHashFunc2());
}

void GeneralObject::addSlot(String* name, Value value, bool) {
    assert(name);

    slots_type::size_type i = slots_.size();
    slots_.push_back(value);
    slot_tbl_.put(object_to_value(name), i, KeyHashFunc2());
}

void GeneralObject::addSlot(String* name, Object* value) {
    assert(name);

    name->ref();
    if (value)
        value->ref();

    slots_type::size_type i = slots_.size();
    slots_.push_back(object_to_value(value));
    slot_tbl_.put(object_to_value(name), i, KeyHashFunc2());
}

void GeneralObject::addSlot(String* name, Object* value, bool) {
    addSlot(name, object_to_value(value), true);
}

Value GeneralObject::opIAdd(Value name, Value v, Context& c, bool is_tail) {
    slots_type::size_type i;
    if (slot_tbl_.get(name, i, KeyHashFunc(c))) {
        Value& vv = slots_[i];

        if (is_object(vv)) {
            if (is_null(vv)) {
                // TODO exception
                return NULL_VALUE;
            }

            Handle<Object> h(value_to_object(vv));

            return vv = h->opAdd(v, c);
        }

        if (is_object(v)) {
            Handle<Object> h(value_to_object(v));
            v = !h.get() ? 0 : h->toInt(c);
        }

        return vv += v;
    }

    Value proto = slots_[0];
    if (object_to_value(this) == proto || !is_object(proto) || is_null(proto))
        return NULL_VALUE;

    return value_to_object(proto)->opIAdd(name, v, c, is_tail);
}

Value GeneralObject::opGetItem(Value idx, Context& c, bool is_tail) {
    // TODO
    return Object::opGetItem(idx, c, is_tail);
}

void GeneralObject::eachChildren(const EachChildrenFunc& fn) {
    slot_tbl_.eachKeys(EachChildrenValueFunc(fn));
    std::for_each(slots_.begin(), slots_.end(), EachChildrenValueFunc(fn));
}

}
